package com.xyb.utils;

import com.xyb.config.exception.MyException;
import com.xyb.entity.StatusCode;
import io.jsonwebtoken.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 容器加载时加载,读取 jwtKey
 */
@Component
public class JwtUtils {
    private static String jwtKey;

    @Value("${config.jwtKey}")
    private void setJwtKey(String jwtKey) {
        this.jwtKey = jwtKey;
    }

    /**
     * 获得一月后的 Date
     *
     * @return
     */
    public static Date getOneMonthAfter() {
        LocalDate now = LocalDate.now();
        LocalDate oneDayAfter = now.plusMonths(1);
        Date date = Date.from(oneDayAfter.atStartOfDay(ZoneId.systemDefault()).toInstant());
        return date;
    }

    /**
     * 内部类,用来传递对象
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class JwtUser {
        private String id;          //唯一标识
        private String subject;     //面向用户
        private String issuer;      //签发者
        private String audience;    //接收者
        private Date issuedAt;      //签发时间
        private Date expiration;    //过期时间
        private Date notBefore;     //在此时间之前不可用

        private Map<String, Object> claims;

        private List<String> roleList;   //角色列表
    }

    /**
     * 生成 token
     *
     * @return
     */
    public static String getToken(JwtUser user) {
        JwtBuilder jb = Jwts.builder();
        //设置载荷
        {
            //设置唯一标识
            jb.setId(user.getId());
            //设置面向用户
            jb.setSubject(user.getSubject());
            //设置签发者
            jb.setIssuer(user.getIssuer());
            //设置接收者
            jb.setAudience(user.getAudience());
            //设置签发时间
            jb.setIssuedAt(user.getIssuedAt() == null ? new Date() : user.getIssuedAt());
            //设置过期时间
            jb.setExpiration(user.getExpiration() == null ? getOneMonthAfter() : user.getExpiration());
            //设置在此时间之前不可用
            jb.setNotBefore(user.getNotBefore());
        }
        //设置自定义声明
        {
            if (user.getClaims() != null) {
                jb.setClaims(user.getClaims());
            }

            if (user.getRoleList() != null && user.getRoleList().size() > 0) {

                jb.claim("roleList", user.getRoleList());
            }
        }
        //设置签名
        {
            jb.signWith(SignatureAlgorithm.HS256, jwtKey);   //设置签名和盐值
        }
        //生成 token
        String token = jb.compact();
        return token;
    }

    /**
     * 解析 token
     *
     * @param token
     */
    public static JwtUser parseToken(String token, String[] keys) {
        JwtParser parser = Jwts.parser();   //解析器

        JwtUser jwtUser = null;
        try {
            Claims claims = parser
                    .setSigningKey(jwtKey)      //设置盐值
                    .parseClaimsJws(token)      //解析token字符串
                    .getBody();

            List roleList = (List) claims.get("roleList");

            Map<String, Object> map = new HashMap<>();
            if (keys != null) {
                for (int i = 0; i < keys.length; i++) {
                    String key = keys[i];
                    map.put(key, claims.get(key));
                }
            }

            jwtUser = JwtUser.builder()
                    .id(claims.getId())
                    .subject(claims.getSubject())
                    .issuer(claims.getIssuer())
                    .audience(claims.getAudience())
                    .issuedAt(claims.getIssuedAt())
                    .expiration(claims.getExpiration())
                    .notBefore(claims.getNotBefore())
                    .claims(map)
                    .roleList(roleList)
                    .build();
        } catch (ExpiredJwtException e) {
            throw new MyException(StatusCode.TOKEN_EXPIRED_EXCEPTION);
        } catch (Exception e) {
            throw new MyException(StatusCode.TOKEN_PARSE_EXCEPTION);
        }
        return jwtUser;
    }

}
